﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Xml.Serialization;
using Reece.Open.Mvc.Atom.Contracts;
using Reece.Open.Mvc.Atom.Models;

namespace Reece.Open.Mvc.Atom
{
    public interface IAtomValidator
    {
        IList<ValidationErrors> ValidateFeed(object feed);
    }

    public class AtomValidator : IAtomValidator
    {
        public IList<ValidationErrors> ValidateFeed(object feed)
        {
            List<ValidationErrors> results = new List<ValidationErrors>();

            Attribute[] attributes = Attribute.GetCustomAttributes(feed.GetType());

            ValidateRoot(attributes, results);

            var elements = GetAllAttributeNames(feed);

            ValidateAuthor(results, elements);
            ValidateGenerator(results, elements);
            ValidateIcon(results, elements);
            ValidateLogo(results, elements);
            ValidateId(results, elements);

            return results;
        }

        private void ValidateId(List<ValidationErrors> errors, IEnumerable<string> elements)
        {
            var ids = elements.Where(x => x == "id");
            int idCount = ids.Count();
            if (idCount > 1)
            {
                errors.Add(ValidationErrors.OnlyOneIdAllowed);
            }
            else if (idCount == 0)
            {
                errors.Add(ValidationErrors.IdRequired);
            }
        }

        private void ValidateLogo(List<ValidationErrors> errors, IEnumerable<string> elements)
        {
            var logos = elements.Where(x => x == "logo");
            if (logos.Count() > 1)
            {
                errors.Add(ValidationErrors.OnlyOneLogoAllowed);
            }
        }

        private void ValidateIcon(List<ValidationErrors> errors, IEnumerable<string> elements)
        {
            var icons = elements.Where(x => x == "icon");
            if (icons.Count() > 1)
            {
                errors.Add(ValidationErrors.OnlyOneIconAllowed);
            }
        }

        private void ValidateGenerator(List<ValidationErrors> errors, IEnumerable<string> elements)
        {
            var generators = elements.Where(x => x == "generator");
            if (generators.Count() > 1)
            {
                errors.Add(ValidationErrors.OnlyOneGeneratorAllowed);
            }
        }

        private void ValidateAuthor(List<ValidationErrors> errors, IEnumerable<string> elements)
        {
            var authors = elements.Where(x => x == "author");
            if (authors.Count() == 0)
            {
                errors.Add(ValidationErrors.AuthorRequired);
            }
        }


        private void ValidateRoot(Attribute[] attributes, List<ValidationErrors> results)
        {
            AtomRoot root = attributes.Where(x => x.GetType() == typeof(AtomRoot)).FirstOrDefault() as AtomRoot;

            if ( root == null || string.IsNullOrWhiteSpace(root.ElementName) || root.ElementName != "feed" )
            {
                results.Add(ValidationErrors.FeedElementRequired);
            }
            if (root == null || string.IsNullOrWhiteSpace(root.Namespace) || root.Namespace != "http://www.w3.org/2005/Atom")
            {
                results.Add(ValidationErrors.NamespaceElementRequired);
            }
        }

        private IEnumerable<string> GetAllAttributeNames(object feed)
        {
            List<string> result = new List<string>();

            var type = feed.GetType();

            FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance);
            foreach (var field in fields)
            {
                var attributes = field.GetCustomAttributes(true).Where(x => x.GetType() == typeof(AtomElement)).Select(x => x as AtomElement);
                if ( attributes.Count() > 0 )
                {
                    result.AddRange(attributes.Select(x => x.ElementName));
                }
                else
                {
                    result.Add(field.Name);
                }
            }

            PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
            foreach (var property in properties)
            {
                var attributes = property.GetCustomAttributes(true).Where(x => x.GetType() == typeof(AtomElement)).Select(x => x as AtomElement);
                if (attributes.Count() > 0)
                {
                    result.AddRange(attributes.Select(x => x.ElementName));
                }
                else
                {
                    result.Add(property.Name);
                }
            }

            return result;
        }
    }
}
